#include "depui/depui.h"

#define MXMODULE_SLIDER

static void mx__scroll_hscroll(MX_SCROLL_DATA * scroll)
{
	if (!scroll->_hscroll) {
		scroll->_hscroll = mx_hslider((MX_SLIDER *) 0, 0, scroll, MXID_SCROLLELEM);
		mx_defaultrect(scroll->_hscroll, 0);
	}
}

static void mx__scroll_vscroll(MX_SCROLL_DATA * scroll)
{
	if (!scroll->_vscroll) {
		scroll->_vscroll = mx_vslider((MX_SLIDER *) 0, 0, scroll, MXID_SCROLLELEM);
		mx_defaultrect(scroll->_vscroll, 0);
	}
}

void mx_scrollcorner_class(void)
{
	MXDEBUG_CLASSINVARIANT(MXOBJ(mx.obj));

	switch (mx.event) {

	case MX_EXPOSE:
		mx__theme->scrollcorner(mx.obj);
		return;

	default:
		break;
	}
	mx_obj_class();
}

void mx__scroll_geometry(MX_SCROLL_DATA * scroll, int x1, int y1, int x2, int y2)
{
	int vsize = 0;
	int hsize = 0;
	unsigned need_v = false;
	unsigned need_h = false;

	MXDEBUG_INVARIANT(MXOBJ(scroll));

	scroll->_x = x1;
	scroll->_y = y1;
	scroll->_w = x2 - x1;
	scroll->_h = y2 - y1 - 1;
	scroll->_vw = mx_w(scroll);
	scroll->_vh = mx_h(scroll) - 1;

	if (scroll->_h > scroll->_vh) {
		mx__scroll_vscroll(scroll);
		MXDEBUG_ALLOCTAG(scroll->_vscroll);

		scroll->_vw -= mx_w(scroll->_vscroll) + 1;
		need_v = true;
	}
	if (scroll->_w > scroll->_vw) {
		mx__scroll_hscroll(scroll);
		MXDEBUG_ALLOCTAG(scroll->_hscroll);

		scroll->_vh -= mx_h(scroll->_hscroll) + 1;
		need_h = true;
	}
	if ((scroll->_h > scroll->_vh) && (!need_v)) {
		mx__scroll_vscroll(scroll);
		MXDEBUG_ALLOCTAG(scroll->_vscroll);

		scroll->_vw -= mx_w(scroll->_vscroll) + 1;
		need_v = true;
	}
	if ((scroll->_w > scroll->_vw) && (!need_h)) {
		mx__scroll_hscroll(scroll);
		MXDEBUG_ALLOCTAG(scroll->_hscroll);

		scroll->_vh -= mx_h(scroll->_hscroll) + 1;
		need_h = true;
	}

	if ((!need_h) && (scroll->_hscroll)) {
		mx_delete(scroll->_hscroll);
		scroll->_hscroll = 0;
	}
	if ((!need_v) && (scroll->_vscroll)) {
		mx_delete(scroll->_vscroll);
		scroll->_vscroll = 0;
	}

	if (scroll->_vscroll) {
		assert(need_v);

		vsize = mx_w(scroll->_vscroll);
		mx_position(scroll->_vscroll, mx_w(scroll) - vsize, 0, vsize, scroll->_vh + 1);
		mx_geometry(scroll->_vscroll);

		mx_slider_set(scroll->_vscroll, scroll->_h, scroll->_vh, -y1);

		MX_TREE_REMOVE(MXOBJ(scroll->_vscroll));
		MX_TREE_INSERT(MXOBJ(scroll->_vscroll), MXOBJ(scroll));
	}
	if (scroll->_hscroll) {
		assert(need_h);

		hsize = mx_h(scroll->_hscroll);
		mx_position(scroll->_hscroll, 0, mx_h(scroll) - hsize, scroll->_vw, hsize);
		mx_geometry(scroll->_hscroll);

		mx_slider_set(scroll->_hscroll, scroll->_w, scroll->_vw, -x1);

		MX_TREE_REMOVE(MXOBJ(scroll->_hscroll));
		MX_TREE_INSERT(MXOBJ(scroll->_hscroll), MXOBJ(scroll));
	}

	if ((scroll->_vscroll) && (scroll->_hscroll)) {
		if (!scroll->_corner) {
			scroll->_corner = mx__obj(0, mx_scrollcorner_class, 0, MXOBJ(scroll), MXID_SCROLLELEM);
			MXDEBUG_ATOMNAME(scroll->_corner, "scrollcorner");
		}

		mx_position(scroll->_corner, mx_w(scroll) - vsize, mx_h(scroll) - hsize, vsize, hsize);

		MX_TREE_REMOVE(MXOBJ(scroll->_corner));
		MX_TREE_INSERT(MXOBJ(scroll->_corner), MXOBJ(scroll));

	} else if (scroll->_corner) {
		mx_delete(scroll->_corner);
		scroll->_corner = 0;
	}
}

static void mx__scroll_geometry_refresh(MX_SCROLL_DATA * scroll)
{
	unsigned int haselem = false;
	int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
	MX_OBJ_DATA *ptr = mx_tree_last(MXOBJ(scroll));

	MXDEBUG_INVARIANT(MXOBJ(scroll));

	while (ptr) {
		MXDEBUG_INVARIANT(ptr);
		if (ptr->_id != MXID_SCROLLELEM) {
			const MX_RECT *pos = MXRECT(ptr);

			if ((!haselem) || (pos->x1 < x1))
				x1 = pos->x1;
			if ((!haselem) || (pos->y1 < y1))
				y1 = pos->y1;
			if ((!haselem) || (pos->x2 > x2))
				x2 = pos->x2;
			if ((!haselem) || (pos->y2 > y2))
				y2 = pos->y2;

			haselem = true;
		}
		ptr = mx_tree_prev(ptr);
	}
	x1 -= mx_x1(scroll);
	y1 -= mx_y1(scroll);
	x2 -= mx_x1(scroll);
	y2 -= mx_y1(scroll);

	mx__scroll_geometry(scroll, x1, y1, x2, y2);
}

static void mx__scroll_do(MX_SCROLL_DATA * scroll, int dx, int dy)
{
	MX_OBJ_DATA *ptr = mx_tree_last(MXOBJ(scroll));

	MXDEBUG_INVARIANT(MXOBJ(scroll));

	while (ptr) {
		MXDEBUG_INVARIANT(ptr);
		if (ptr->_id != MXID_SCROLLELEM) {
			MX_RECT pos;

			pos = *MXRECT(ptr);
			pos.x1 -= dx;
			pos.y1 -= dy;
			pos.x2 -= dx;
			pos.y2 -= dy;
			mx_rectatom_place(ptr, &pos);
		}
		ptr = mx_tree_prev(ptr);
	}

	scroll->_x -= dx;
	scroll->_y -= dy;
}

static void mx__scrollarea(MX_SCROLL_DATA * scroll, const MX_RECT * rrect)
{
	if (!rrect)
		return;

	if (scroll->_vscroll) {
		const int y1d = scroll->_y + rrect->y1;
		const int y2d = scroll->_y - scroll->_vh + rrect->y2 - 1;

		if (y1d < 0)
			mx_slider_to(scroll->_vscroll, rrect->y1);
		else if (y2d > 0)
			mx_slider_to(scroll->_vscroll, rrect->y2 - scroll->_vh - 1);

		mx_event(scroll, MX_VSCROLL, 0, 0);
	}

	if (scroll->_hscroll) {
		const int x1d = scroll->_x + rrect->x1;
		const int x2d = scroll->_x - scroll->_vw + rrect->x2 - 1;

		if (x1d < 0)
			mx_slider_to(scroll->_hscroll, rrect->x1);
		else if (x2d > 0)
			mx_slider_to(scroll->_hscroll, rrect->x2 - scroll->_vw - 1);

		mx_event(scroll, MX_HSCROLL, 0, 0);
	}
}

void mx_scroll_class(void)
{
	MX_SCROLL *s = (MX_SCROLL *) mx.obj;
	MX_SCROLL_DATA *scroll = MXSCROLL(s);

	MXDEBUG_CLASSINVARIANT(MXOBJ(s));

	switch (mx.event) {

	case MX_EXPOSE:
		mx__theme->scroll(s);
		return;

	case MX_GEOMETRY:
		mx__scroll_geometry_refresh(scroll);
		return;

	case MX_HSCROLL:
		if (scroll->_hscroll) {
			const int dx = mx_slider_value(scroll->_hscroll) + scroll->_x;

			mx__scroll_do(scroll, dx, 0);
			mx_dirty(scroll, true);
		}
		break;

	case MX_VSCROLL:
		if (scroll->_vscroll) {
			const int dy = mx_slider_value(scroll->_vscroll) + scroll->_y;

			mx__scroll_do(scroll, 0, dy);
			mx_dirty(scroll, true);
		}
		break;

	case MX_SCROLLAREA:
		mx__scrollarea(scroll, (const MX_RECT *) mx.data);
		break;

	default:
		break;
	}
	mx_obj_class();
}

MX_SCROLL *mx__scroll(MX_SCROLL_DATA * scroll, size_t size, MX_OBJ_DATA * parent, int theid)
{
	MXMINSIZE(size, MX_SCROLL);

	scroll = mx__obj(MXOBJ(scroll), mx_scroll_class, size, parent, theid);
	if (scroll) {
		MXDEBUG_ALLOCTAG(scroll);
		MXDEBUG_ATOMNAME(scroll, "scroll");
	}
	MXDEBUG_INVARIANT(MXOBJ(scroll));
	return (MX_SCROLL *) scroll;
}

void mx__scroll_reset(MX_SCROLL_DATA * scroll)
{
	int x = 0, y = 0;
	int started = false;
	MX_OBJ_DATA *ptr = mx_tree_last(MXOBJ(scroll));

	MXDEBUG_INVARIANT(MXOBJ(scroll));

	/* Look thorough Find the first element to scroll */
	while (ptr) {
		MXDEBUG_INVARIANT(ptr);
		if (ptr->_id != MXID_SCROLLELEM) {
			if (!started) {
				x = mx__x(ptr);
				y = mx__y(ptr);
				started = true;
			} else {
				x = MX_MIN(x, mx__x(ptr));
				y = MX_MIN(y, mx__y(ptr));
			}
		}
		ptr = mx_tree_prev(ptr);
	}
	mx__scroll_do(scroll, x, y);
}
